#include "nonos.h"
#include <string.h>

#define array_len(arr)          (sizeof(arr) / sizeof(arr[0]))
#define foreach_array(idx, arr) for (idx = 0; idx < array_len(arr); idx++)

typedef enum {
    TASK_TIMER = 0,
    TASK_EVENT,
    TASK_TYPE_NUM,
} nos_task_type_t;

struct nos_task_tcb_t {
    nos_task_type_t type;
    bool            used;
    void*           task_func;
    void*           task_args;
    union {
        struct {
            uint32_t period;
            uint32_t waketime;
        };
        struct {
            nos_evmap_t event_care;
            nos_evmap_t event_recv;
        };
    };
};

static struct nos_task_tcb_t nos_task_pool[NOS_TASK_NUM_MAX];
static uint64_t              nos_tickcnt = 0;
static bool                  nos_trigger = true;
static bool                  nos_inited  = false;

nos_task_t nos_event_task_create(nos_event_func_t task_func, void* task_args, nos_evmap_t events)
{
    uint8_t idx;

    if (task_func == nullptr || events == 0) {
        return nullptr;
    }

    foreach_array(idx, nos_task_pool)
    {
        if (nos_task_pool[idx].used == false) {
            nos_task_pool[idx].used       = true;
            nos_task_pool[idx].type       = TASK_EVENT;
            nos_task_pool[idx].task_func  = task_func;
            nos_task_pool[idx].task_args  = task_args;
            nos_task_pool[idx].event_care = events;

            return &nos_task_pool[idx];
        }
    }

    return nullptr;
}

nos_task_t nos_timer_task_create(nos_timer_func_t task_func, void* task_args, uint32_t period, uint32_t delay)
{
    uint8_t idx;
    uint8_t event_no;

    if (task_func == nullptr || period == 0) {
        return nullptr;
    }

    foreach_array(idx, nos_task_pool)
    {
        if (nos_task_pool[idx].used == false) {
            nos_task_pool[idx].used      = true;
            nos_task_pool[idx].type      = TASK_TIMER;
            nos_task_pool[idx].task_func = task_func;
            nos_task_pool[idx].task_args = task_args;
            nos_task_pool[idx].period    = period;
            nos_task_pool[idx].waketime  = nos_tickcnt + delay;
            return &nos_task_pool[idx];
        }
    }

    return nullptr;
}

void nos_task_delete(nos_task_t task)
{
    if (task) {
        task->used = false;
    }
}

int nos_event_post(nos_task_t task, nos_evmap_t event)
{
    if (task == nullptr) {
        return -1;
    }
    task->event_recv |= event;
    nos_trigger = true;

    return 0;
}

void nos_systick(void)
{
    if (nos_inited) {
        nos_tickcnt++;
        nos_trigger = true;
    }
}

uint32_t nos_timestamp(void)
{
    return nos_tickcnt;
}

int nos_main()
{
    if (!nos_inited) {
        memset(&nos_task_pool, 0, sizeof(nos_task_pool));
        while (nos_user_init() != 0) {
            return -1;
        }
        nos_tickcnt = 0;
        nos_inited  = true;
        // nos_trigger = true;
    }

    if (nos_trigger) {
        nos_trigger = false;

        uint8_t idx;
        foreach_array(idx, nos_task_pool)
        {
            if (nos_task_pool[idx].used) {
                if (nos_task_pool[idx].type == TASK_TIMER) {
                    if (nos_tickcnt >= nos_task_pool[idx].waketime) {
                        ((nos_timer_func_t)nos_task_pool[idx].task_func)(nos_task_pool[idx].task_args);
                        nos_task_pool[idx].waketime += nos_task_pool[idx].period;
                    }
                } else if (nos_task_pool[idx].type == TASK_EVENT) {
                    if (nos_task_pool[idx].event_recv & nos_task_pool[idx].event_care) {
                        ((nos_event_func_t)nos_task_pool[idx].task_func)(nos_task_pool[idx].event_recv,
                                                                         nos_task_pool[idx].task_args);
                        nos_task_pool[idx].event_recv = 0;
                    }
                }
            }
        }
    }
    return 0;
}
